#include <fstream>
using namespace std;

#include "dataspace.h"

DataSpace::DataSpace() 
{
  data_field = 0;
}

void DataSpace::set_data_field(DataField* data_field) 
{
  this->data_field = data_field;
}

void DataSpace::set_value__(const AxesCoor& coor, 
			    const DynObject& dd,
			    bool axes_mod_f) 
{
  for (size_t i_dim = 0; i_dim < axes.get_n_dim(); i_dim++) {
    Axis* axis = axes.get_at(i_dim);
    if (axis->find_tic(coor.get_at(i_dim), 0, 0) == false) {
      if (axes_mod_f) {
	vector<data_index_t> new_index;
	data_field->allocate_indexes(i_dim, 1, &new_index);
	axes.get_at(i_dim)->add_tic(coor.get_at(i_dim), new_index[0]);
      } else {
	throw Error(__FILE__, __LINE__, 
		    "DataSpace::set_value: no coor and no mod allowed");
      }
    }
  }
  
  vector<axis_index_t> ai_coor = axes.get_axis_indexes_coor(coor);
  vector<data_index_t> data_coor = axes.get_data_indexes_coor(ai_coor);

  data_field->set_value(data_coor, dd);
}

void DataSpace::create_datafield(const string& datafield_fn,
				 DynType* dt, 
				 const vector<data_index_t>& max_shape) 
{
  this->datafield_fn = datafield_fn;
  this->data_field = new FileDataField(dt, max_shape, datafield_fn.c_str(), 
				       true);
}

bool DataSpace::load(const string& ds_fn, bool writable)
{
  datafield_fn = ds_fn;
  string axes_fn = ds_fn + ".AXES";
  ifstream axes_in(axes_fn.c_str());
  this->axes.load(axes_in);
  //ds.axes.print(cout);
  this->data_field = new FileDataField();
  this->data_field->load(datafield_fn, writable);
  return true;
}

void DataSpace::save()
{
  string axes_fn = datafield_fn + ".AXES";
  ofstream out(axes_fn.c_str());
  this->axes.save(out);
  this->data_field->save();
}

void DataSpace::dump() const
{
  this->print(cout);
}

void DataSpace::print(ostream& out) const
{
  axes.print(out);
  data_field->print(out);
}

class FillCB : public NDimRangeTraverseCB
{
public:
  const DataSpace* ds;
  vector<AxesCoor>* coors;
  vector<DynObject>* values;
  FillCB(const DataSpace* ds, vector<AxesCoor>* coors, vector<DynObject>* values)
  {
    this->ds = ds;
    this->coors = coors;
    this->values = values;
  }

  void do_it(const vector<size_t>& axis_indexes_coor) {
    vector<data_index_t> data_indexes_coor = ds->axes.get_data_indexes_coor(axis_indexes_coor);
    DynObject oo;
    ds->get_data_field()->get_value(data_indexes_coor, &oo);
    values->push_back(oo);

    AxesCoor c(&ds->axes, axis_indexes_coor);
    coors->push_back(c);
  }
};

bool
DataSpace::onedim_range(vector<pair<axis_index_t, axis_index_t> >& range_axis_indexes,
			vector<AxesCoor>* coors,
			vector<DynObject>* values) const
{
  NDimRange ndim_region(range_axis_indexes, axes.get_shape());
  FillCB cb(this, coors, values);
  ndim_region.traverse(&cb);
  return true;
}

bool
DataSpace::is_none_at_coor(const AxesCoor& coor) const
{
  if (axes.does_coor_exist(coor)) {
    vector<axis_index_t> axis_indexes_coor = axes.get_axis_indexes_coor(coor);
    vector<data_index_t> data_indexes_coor = axes.get_data_indexes_coor(axis_indexes_coor);
    return data_field->is_none_value(data_indexes_coor);
  }
  return true;
}

// ...............................................................

DataSpaceRangeTraverseCB::DataSpaceRangeTraverseCB()
{
  dataspace_range = 0;
}

DataSpaceRange::DataSpaceRange(DataSpace* ds) :
  axes_range(&ds->axes)
{
  dataspace = ds;
}

void DataSpaceRange::set_left(const string& dim, const DynObject& l)
{
  axes_range.set_left(dim, l);
}

void DataSpaceRange::set_right(const string& dim, const DynObject& r)
{
  axes_range.set_right(dim, r);
}

void DataSpaceRange::set_left_right(const string& dim, 
				    const DynObject& l, const DynObject& r)
{
  axes_range.set_left(dim, l);
  axes_range.set_right(dim, r);
}

void DataSpaceRange::set_left_right(const string& dim, const DynObject& lr)
{
  axes_range.set_left(dim, lr);
  axes_range.set_right(dim, lr);
}

// .....................................................................

class ValueAccessCB : public DataSpaceRangeTraverseCB
{
public:
    vector<DynObject>* out;
    vector<AxesCoor>* coors_out;
    ValueAccessCB(vector<DynObject>* out,
		  vector<AxesCoor> * coors_out)
  {
      this->out = out;
      this->coors_out = coors_out;
  }

  void do_it(const vector<size_t>& coor) {
    // coor is axis_indexes_coor
    DataSpace* dataspace = dataspace_range->dataspace;
    vector<data_index_t> data_indexes_coor = dataspace->axes.get_data_indexes_coor(coor);
    DynObject o;
    dataspace->get_data_field()->get_value(data_indexes_coor, &o);
    if (o.is_none() == false) {
	out->push_back(o);
    }
  }
};


void DataSpaceRange::get_values(vector<DynObject>* out,
				vector<AxesCoor>* coors_out) const
{
    ValueAccessCB cb(out, coors_out);
    ((DataSpaceRange*)this)->traverse(&cb);
}

class PCB : public DataSpaceRangeTraverseCB
{
public:
  ostream* out;
  PCB(ostream& out)
  {
    this->out = &out;
  }

  void do_it(const vector<size_t>& coor) {
    // coor is axis_indexes_coor
    DataSpace* dataspace = dataspace_range->dataspace;
    vector<data_index_t> data_indexes_coor = dataspace->axes.get_data_indexes_coor(coor);
    DynObject o;
    dataspace->get_data_field()->get_value(data_indexes_coor, &o);
    if (o.is_none() == false) {
      o.print(*out);
    }
  }
};

void DataSpaceRange::traverse(DataSpaceRangeTraverseCB* cb)
{
  vector<pair<size_t, size_t> > range_axis_indexes_range = axes_range.get_axis_indexes_range();

  NDimRange r(range_axis_indexes_range, dataspace->get_data_field()->get_shape());
  if (cb) {
    cb->dataspace_range = this;
    r.traverse(cb);
  } else {
    PCB pcb(cout);
    pcb.dataspace_range = this;
    r.traverse(&pcb);
  }
}


